package com.yungu.swift.order.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.alicom.mns.tools.DefaultAlicomMessagePuller;
import com.alicom.mns.tools.MessageListener;
import com.aliyun.mns.model.Message;
import com.aliyuncs.dyplsapi.model.v20170525.BindAxbResponse;
import com.yungu.swift.autoconfig.redis.RedisCacheService;
import com.yungu.swift.base.ResponseData;
import com.yungu.swift.base.mapper.IMapper;
import com.yungu.swift.base.service.impl.BaseServiceImpl;
import com.yungu.swift.order.config.ApplicationConfig;
import com.yungu.swift.order.dao.OrderSecretNoMapper;
import com.yungu.swift.order.model.dto.OrderSecretNoDto;
import com.yungu.swift.order.model.dto.OrderSecretNoReportDto;
import com.yungu.swift.order.model.enums.SecretReportEnum;
import com.yungu.swift.order.model.param.SecretNoParam;
import com.yungu.swift.order.service.OrderSecretNoReportService;
import com.yungu.swift.order.service.OrderSecretNoService;
import com.yungu.swift.order.utils.SecretNoUtils;
import com.yungu.swift.oss.OSSTemplate;
import com.yungu.swift.oss.annotation.OSSEntrance;
import com.yungu.swift.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.asynchttpclient.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;

import static com.yungu.swift.order.config.NacosListener.ALIYUN_CONFIG;

@Slf4j
@Service
@org.springframework.stereotype.Service
@ConfigurationProperties("swift.aliyun")
public class OrderSecretNoServiceImpl extends BaseServiceImpl<OrderSecretNoDto> implements OrderSecretNoService {

    private static final String REDIS_PREFIX = "secret_no_";
    /**
     * 小号话单消息类型
     */
    private static final String SECRET_REPORT = "SecretReport";
    /**
     * 录音消息类型
     */
    private static final String SECRET_RECORDING = "SecretRecording";

    private String accessKey;
    private String secretKey;

    @Autowired
    private OrderSecretNoMapper orderSecretNoMapper;
    @Autowired
    private OrderSecretNoReportService orderSecretNoReportService;
    @Autowired
    private RedisCacheService redisCacheService;
    @OSSEntrance(bucketName = "")
    private OSSTemplate ossTemplate;

    private List<DefaultAlicomMessagePuller> pullers = new ArrayList<>();

    //@PostConstruct
    public void init() {
        if (!ALIYUN_CONFIG.getRecording()) {
            return;
        }
        if (CollectionUtils.isNotEmpty(pullers)) {
            pullers.forEach(DefaultAlicomMessagePuller::stop);
            pullers.clear();
        }
        accessKey = ParamUtil.defaultIfNull(accessKey, ALIYUN_CONFIG.getAccessKeyId());
        secretKey = ParamUtil.defaultIfNull(secretKey, ALIYUN_CONFIG.getAccessKeySecret());
        try {
            Set<String> recordingSet = org.springframework.util.StringUtils.commaDelimitedListToSet(ALIYUN_CONFIG.getSecretRecordingQueue());
            Set<String> reportSet = org.springframework.util.StringUtils.commaDelimitedListToSet(ALIYUN_CONFIG.getSecretReportQueue());

            for (String recordingQueue : recordingSet) {
                log.info("start listening {}", recordingQueue);
                DefaultAlicomMessagePuller puller = new DefaultAlicomMessagePuller();
                puller.startReceiveMsg(accessKey, secretKey, SECRET_RECORDING, recordingQueue, new SecretRecordingMessageListener());
                pullers.add(puller);
            }

            for (String reportQueue : reportSet) {
                log.info("start listening {}", reportQueue);
                DefaultAlicomMessagePuller puller = new DefaultAlicomMessagePuller();
                puller.startReceiveMsg(accessKey, secretKey, SECRET_REPORT, reportQueue, new SecretReportMessageListener());
                pullers.add(puller);
            }

        } catch (Exception e) {
            log.error("consume mns message error.", e);
        }
    }


    @Override
    protected IMapper<OrderSecretNoDto> getMapper() {
        return orderSecretNoMapper;
    }

    @Override
    public ResponseData<String> addBingding(SecretNoParam secretNoParam) {
        if (!ApplicationConfig.COMMON_CONFIG.getPrivacyNumber()) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "隐私号关闭");
        }
        OrderSecretNoDto secretNoDto = new OrderSecretNoDto();
        secretNoDto.setUuid(StringUtils.buildUUID());
        secretNoDto.setPassengerMobile(secretNoParam.getPassengerMobile());
        secretNoDto.setAppid(secretNoParam.getAppid());
        secretNoDto.setOrderUuid(secretNoParam.getOrderUuid());
        secretNoDto.setDriverMobile(secretNoParam.getDriverMobile());
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR, secretNoParam.getExpiration());
        secretNoDto.setFailureTime(calendar.getTime());
        secretNoDto.setStatus(1);
        //调用阿里云新增隐私号码绑定 如果绑定成功 则保存到数据库 并加入缓存
        BindAxbResponse.SecretBindDTO secretBindDTO = SecretNoUtils.bindAxb(secretNoDto);
        if (secretBindDTO != null) {
            secretNoDto.setPrivacyUuid(secretBindDTO.getSubsId());
            secretNoDto.setPrivacyMobile(secretBindDTO.getSecretNo());
            CompletableFuture.runAsync(() -> orderSecretNoMapper.add(secretNoDto));
            log.info("隐私号绑定成功，orderUuid：{}，NoA：{}，NoB：{}，NoX：{}", secretNoParam.getOrderUuid(),
                    secretNoParam.getPassengerMobile(), secretNoParam.getDriverMobile(), secretNoDto.getPrivacyMobile());
            //将隐私号关系绑定到缓存里面
            cache(secretNoDto.getOrderUuid(), secretNoDto.getPrivacyMobile(), secretNoParam.getExpiration());
            return ResponseData.buildSuccessResponse("操作成功", secretNoDto.getPrivacyMobile());
        }
        return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "操作失败");
    }

    @Override
    public ResponseData<String> updateNoB(SecretNoParam secretNoParam) {
        if (!ApplicationConfig.COMMON_CONFIG.getPrivacyNumber()) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "隐私号关闭");
        }
        if (ParamUtil.isIllegal(secretNoParam.getDriverMobile(), secretNoParam.getOrderUuid())) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "必填参数不允许为空");
        }
        //通过订单ID查询隐私小号绑定记录
        List<OrderSecretNoDto> list = orderSecretNoMapper.list(MapUtils.build("orderUuid", secretNoParam.getOrderUuid()));
        if (CollectionUtils.isEmpty(list)) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "未在系统中找到隐私号码绑定关系");
        }
        //遍历绑定记录 更新阿里云的司机绑定号码关系
        OrderSecretNoDto secretNoDto = list.get(0);
        secretNoDto.setDriverMobile(secretNoParam.getDriverMobile());
        //调用阿里云 更新司机号码 失败则清除缓存
        if (SecretNoUtils.updateNoB(secretNoDto)) {
            CompletableFuture.runAsync(() -> orderSecretNoMapper.edit(secretNoDto));
            //更新缓存
            cache(secretNoDto.getOrderUuid(), secretNoDto.getPrivacyMobile(), secretNoParam.getExpiration());
            log.info("更新隐私号司机号码成功，orderUuid：{}，driverMobile：{}", secretNoParam.getOrderUuid(), secretNoParam.getDriverMobile());
            return ResponseData.buildSuccessResponse(secretNoDto.getPrivacyMobile());
        }
        redisCacheService.delete(REDIS_PREFIX + secretNoParam.getOrderUuid());
        return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "操作失败");
    }

    @Override
    public ResponseData<Boolean> updateExpire(SecretNoParam secretNoParam) {
        if (!ApplicationConfig.COMMON_CONFIG.getPrivacyNumber()) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "隐私号关闭");
        }
        if (StringUtils.isEmpty(secretNoParam.getOrderUuid())) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "orderUuid 不允许为空");
        }
        List<OrderSecretNoDto> list = this.orderSecretNoMapper.list(MapUtils.build("orderUuid", secretNoParam.getOrderUuid()));
        if (CollectionUtils.isEmpty(list)) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "查找号码绑定失败");
        }
        OrderSecretNoDto secretNoDto = list.get(0);
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR, 2);
        secretNoDto.setFailureTime(calendar.getTime());
        secretNoDto.setStatus(2);
        if (SecretNoUtils.updateExpire(secretNoDto)) {
            CompletableFuture.runAsync(() -> orderSecretNoMapper.edit(secretNoDto));
            //更新缓存的超时时间 查询时 只会从缓存获取
            cache(secretNoDto.getOrderUuid(), secretNoDto.getPrivacyMobile(), 2);
            log.info("更新隐私号过期时间成功，orderUuid：{}", secretNoParam.getOrderUuid());
            return ResponseData.buildSuccessResponse("操作成功", Boolean.TRUE);
        }
        return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "操作失败");
    }

    @Override
    public ResponseData<Void> unbind(String orderUuid) {
        if (!ApplicationConfig.COMMON_CONFIG.getPrivacyNumber()) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "隐私号关闭");
        }
        if (StringUtils.isEmpty(orderUuid)) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "orderUuid 不允许为空");
        }
        //通过订单ID解绑该订单所有绑定关系
        List<OrderSecretNoDto> list = this.orderSecretNoMapper.list(MapUtils.build("orderUuid", orderUuid));
        if (CollectionUtils.isEmpty(list)) {
            log.warn("订单：{} 解绑隐私号失败，查找号码绑定失败", orderUuid);
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "查找号码绑定失败");
        }
        OrderSecretNoDto secretNoDto = list.get(0);
        SecretNoUtils.unbind(secretNoDto);
        secretNoDto.setFailureTime(new Date());
        secretNoDto.setStatus(2);
        //解绑隐私号的时候 则直接清除缓存
        redisCacheService.delete(REDIS_PREFIX + orderUuid);
        orderSecretNoMapper.edit(secretNoDto);
        return ResponseData.buildSuccessResponse("解绑完成", null);
    }

    @Override
    public ResponseData<Map<String, String>> queryBinding(SecretNoParam secretNoParam) {
        Object privacyMobile = redisCacheService.get(REDIS_PREFIX + secretNoParam.getOrderUuid());
        if (privacyMobile == null) {
            List<OrderSecretNoDto> list = orderSecretNoMapper.list(MapUtils.build("orderUuid", secretNoParam.getOrderUuid()));
            if (CollectionUtils.isEmpty(list)) {
                return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "查找号码绑定失败");
            }
            OrderSecretNoDto secretNoDto = list.get(0);
            privacyMobile = secretNoDto.getPrivacyMobile();
            cache(secretNoDto.getOrderUuid(), secretNoDto.getPrivacyMobile(), 24);
        }
        if (privacyMobile == null) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "系统未找到隐私小号");
        }
        Map<String, String> map = new HashMap<>(2);
        map.put("privacyMobile", privacyMobile.toString());
        return ResponseData.buildSuccessResponse(map);
    }

    /**
     * 将隐私号码绑定加入缓存
     */
    private void cache(String orderUuid, String secretNo, Integer expire) {
        this.redisCacheService.set(REDIS_PREFIX + orderUuid, secretNo, Long.valueOf(expire * 60));
    }

    /**
     * 处理录音消息类型
     */
    private class SecretRecordingMessageListener implements MessageListener {

        @Override
        public boolean dealMessage(Message message) {
            String messageBodyAsString = message.getMessageBodyAsString();
            try {
                Map<String, Object> recordMap = JsonUtils.readValue(messageBodyAsString, HashMap.class);
                log.info("handle SecretRecord info. {}", recordMap);
                String callId = String.valueOf(recordMap.get("call_id"));
                if (StringUtils.isEmpty(callId)) {
                    return false;
                }
                OrderSecretNoReportDto reportDto = orderSecretNoReportService.get(MapUtils.build("callId", callId)).getData();
                if (reportDto == null) {
                    log.warn("callId:{} mysql record is null", callId);
                    return false;
                }
                String downloadUrl = SecretNoUtils.queryRecordFileDownloadUrl(callId, reportDto.getCallTime());
                if (null == downloadUrl) {
                    log.warn("callId:{} download_url is null", callId);
                    return true;
                }
                reportDto.setDownloadUrl(downloadUrl);
                downloadAndUploadToOss(downloadUrl, reportDto);
            } catch (IOException e) {
                //理论上不会出现格式错误的情况，所以遇见格式错误的消息，只能先delete,否则重新推送也会一直报错
                log.error("secret_record_error_json_format:" + messageBodyAsString, e);
                return true;
            } catch (Throwable e) {
                //您自己的代码部分导致的异常，应该return false,这样消息不会被delete掉，而会根据策略进行重推
                log.error("secret record handle aliyun message error", e);
                return false;
            }
            //消息处理成功，返回true, SDK将调用MNS的delete方法将消息从队列中删除掉
            return true;
        }
    }

    private void downloadAndUploadToOss(String downloadUrl, OrderSecretNoReportDto reportDto) throws IOException {
        AsyncHttpClient client = new DefaultAsyncHttpClient();
        client.prepareGet(downloadUrl).execute(new OssUploadHandler(getDownloadFilename(downloadUrl), reportDto));
    }

    private class OssUploadHandler extends AsyncCompletionHandler<Boolean> {
        FileOutputStream stream;
        OrderSecretNoReportDto reportDto;
        String filename;
        File localFile;

        OssUploadHandler(String filename, OrderSecretNoReportDto reportDto) throws IOException {
            this.filename = filename;
            this.reportDto = reportDto;
            // create temp file
            this.localFile = File.createTempFile(filename, ".log");
            this.stream = new FileOutputStream(localFile);
        }

        @Override
        public State onBodyPartReceived(HttpResponseBodyPart content) throws Exception {
            stream.getChannel().write(content.getBodyByteBuffer());
            return State.CONTINUE;
        }

        @Override
        public Boolean onCompleted(Response response) throws Exception {
            stream.close();
            String ossFileName = getOssFileName(filename, reportDto);
            ossTemplate.setBucketName(ALIYUN_CONFIG.getCallRecordBucketName());
            ossTemplate.upload(ossFileName, localFile);
            // save to db
            reportDto.setOssDownloadUrl(ossFileName);
            reportDto.setUpdateOn(new Date());
            orderSecretNoReportService.edit(reportDto);
            // remove local file
            FileUtils.deleteQuietly(this.localFile);
            return Boolean.TRUE;
        }
    }

    private String getDownloadFilename(String url) {
        if (StringUtils.isBlank(url)) {
            return "";
        }
        int end = url.indexOf('?');
        String newUrl = url.substring(0, end);
        int start = newUrl.lastIndexOf('/');
        return newUrl.substring(start + 1, end);
    }

    private String getOssFileName(String localFile, OrderSecretNoReportDto reportDto) {
        return String.format("%s/%s/%s", DateUtils.format(reportDto.getCreateOn(), "yyyy-MM-dd"),
                reportDto.getOrderUuid(), localFile);
    }

    /**
     * 处理通过记录消息类型
     */
    private class SecretReportMessageListener implements MessageListener {

        @Override
        public boolean dealMessage(Message message) {
            String messageBodyAsString = message.getMessageBodyAsString();
            try {
                Map<String, Object> reportMap = JsonUtils.readValue(messageBodyAsString, HashMap.class);
                log.info("handle SecretReport info. {}", reportMap);
                String outId = String.valueOf(reportMap.get("out_id"));
                if (StringUtils.isEmpty(outId)) {
                    return false;
                }
                Map<String, String> extMap = JsonUtils.readValue(outId, HashMap.class);
                OrderSecretNoReportDto reportDto = new OrderSecretNoReportDto();
                reportDto.setUuid(StringUtils.buildUUID());
                reportDto.setOrderUuid(extMap.get("orderUuid"));
                reportDto.setAppid(extMap.get("appid"));
                reportDto.setPrivacyUuid(String.valueOf(reportMap.get("sub_id")));
                reportDto.setCallId(String.valueOf(reportMap.get("call_id")));
                reportDto.setPassengerMobile(String.valueOf(reportMap.get("phone_no")));
                reportDto.setPrivacyMobile(String.valueOf(reportMap.get("secret_no")));
                reportDto.setPeerNo(String.valueOf(reportMap.get("peer_no")));
                reportDto.setCallType((Integer) reportMap.get("call_type"));
                reportDto.setSmsNumber((Integer) reportMap.get("sms_number"));
                reportDto.setReleaseDir((Integer) reportMap.get("release_dir"));
                reportDto.setReleaseCause(SecretReportEnum.getReleaseCause(reportMap.get("release_cause")));
                reportDto.setCallTime(reportMap.get("call_time") == null ? null : DateUtils.parse(
                        (String) reportMap.get("call_time")));
                reportDto.setStartTime(reportMap.get("start_time") == null ? null : DateUtils.parse(
                        (String) reportMap.get("start_time")));
                reportDto.setRingTime(reportMap.get("ring_time") == null ? null : DateUtils.parse(
                        (String) reportMap.get("ring_time")));
                reportDto.setFreeRingTime(reportMap.get("free_ring_time") == null ? null : DateUtils.parse(
                        (String) reportMap.get("free_ring_time")));
                reportDto.setReleaseTime(reportMap.get("release_time") == null ? null : DateUtils.parse(
                        (String) reportMap.get("release_time")));
                reportDto.setCreateOn(new Date());
                orderSecretNoReportService.add(reportDto);
            } catch (IOException e) {
                //理论上不会出现格式错误的情况，所以遇见格式错误的消息，只能先delete,否则重新推送也会一直报错
                log.error("secret_report_error_json_format:" + messageBodyAsString, e);
                return true;
            } catch (Throwable e) {
                //您自己的代码部分导致的异常，应该return false,这样消息不会被delete掉，而会根据策略进行重推
                log.error("secret record handle aliyun message error", e);
                return false;
            }
            //消息处理成功，返回true, SDK将调用MNS的delete方法将消息从队列中删除掉
            return true;
        }
    }


    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }
}